﻿using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Security.Principal;
using System.Security.Cryptography;
using System.IO;
using System.Text;

namespace Ruandao.Web
{
    /// <summary>
    /// 大文件上传与下载的工具类。
    /// </summary>
    /// <remarks>
    /// 该类提供了大文件上传与下载的方法。\n
    /// 支持断点、多线程下载。\n
    /// 该下载方法在迅雷5.0下测试正常。
    /// </remarks>
    public class FileUtil
    {
        private const string Header_Content_MD5 = "Content-MD5";
        private const string Header_Accept_Ranges = "Accept-Ranges";
        private const string Header_ETag = "ETag";
        private const string Header_Last_Modified = "Last-Modified";
        private const string Header_Content_Disposition = "Content-Disposion";
        private const string Header_Content_Disposition_Value_Format = "attachment;filename={0}";
        private const string Header_Content_Length = "Content-Length";
        private const string Header_Connection = "Connection";
        private const string Header_Content_Range = "Content-Range";
        private const string Header_Content_Range_Value_Format = "bytes {0}-{1}/{2}";
        private const string Header_Range = "Range";
        private const string Header_If_Range = "If-Range";

        private const string Content_Type = "application/octet-stream";

        private int _cacheSize;
        public int CacheSize
        {
            get
            {
                return _cacheSize;
            }
            set
            {
                if (value >= 0)
                    _cacheSize = value;
            }
        }

        public FileUtil()
        {
            _cacheSize = 1024 * 10; // 10K
        }

        public FileUtil(int cacheSize)
            : this()
        {
            this.CacheSize = cacheSize;
        }

        /// <summary>
        /// 下载文件。
        /// </summary>
        /// <remarks>
        /// 默认文件大小限制为：Int64.MaxValue
        /// </remarks>
        /// <param name="context">Http上下文</param>
        /// <param name="filePath">要下载的文件路径</param>
        /// <param name="speed">下载速度</param>
        /// <returns></returns>
        public bool DownLoad(HttpContext context, string filePath, int speed)
        {
            return DownLoad(context, filePath, speed, Int64.MaxValue);
        }

        /// <summary>
        /// 下载文件。
        /// </summary>
        /// <param name="context">Http上下文</param>
        /// <param name="filePath">要下载的文件路径</param>
        /// <param name="speed">下载速度</param>
        /// <param name="maxSize">文件大小限制</param>
        /// <returns></returns>
        public bool DownLoad(HttpContext context, string filePath, int speed, long maxSize)
        {
            HttpRequest Request = context.Request;
            HttpResponse Response = context.Response;

            switch (Request.HttpMethod.ToUpper())
            {
                case "GET":
                case "HEAD":
                    break;
                default:
                    Response.StatusCode = 501;  // 未实现的配置。
                    return false;
            }

            if (!File.Exists(filePath))
            {
                Response.StatusCode = 401;  // 请求的资源不存在。
                return false;
            }

            int sleepTime = CacheSize * 1000 / speed;
            long fileLength = 0;
            long offset = 0;
            string fileName = Path.GetFileName(filePath);
            string lastWriteTime = File.GetLastWriteTimeUtc(filePath).ToString("u");
            FileStream inStream = null;

            try
            {
                inStream = File.OpenRead(filePath);
                long a = inStream.Position;
                fileLength = inStream.Length;
                long b = inStream.Position;

                if (fileLength > maxSize)
                {
                    Response.StatusCode = 413;  // 请求实体太长。
                    return false;
                }
                string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastWriteTime;
                if (Request.Headers[Header_If_Range] != null)
                {
                    if (Request.Headers[Header_If_Range].Replace("\"", "") != eTag)
                    {
                        Response.StatusCode = 412;  // 请求资源已经发生变化。
                        return false;
                    }
                }

                Response.Clear();
                Response.Buffer = false;   // 数据不在服务器端缓存。
                Response.ContentType = Content_Type;
                Response.ContentEncoding = Encoding.UTF8;
                Response.AddHeader(Header_Content_MD5, GetMD5Hash(inStream));
                Response.AddHeader(Header_Accept_Ranges, "bytes");
                Response.AddHeader(Header_ETag, "\"" + eTag + "\"");
                Response.AddHeader(Header_Last_Modified, lastWriteTime);
                Response.AddHeader(Header_Content_Disposition, String.Format(Header_Content_Disposition_Value_Format, HttpUtility.UrlEncode(fileName, Encoding.UTF8)));
                Response.AddHeader(Header_Content_Length, (fileLength - offset).ToString());
                Response.AddHeader(Header_Connection, "Keep-Alive");

                if (Request.Headers[Header_Range] != null)
                {
                    Response.StatusCode = 206;  // 重要：表示部分内容。
                    string[] temp = Request.Headers[Header_Range].Split(new char[] { '=', '-' });
                    try
                    {
                        offset = long.Parse(temp[1]);
                    }
                    catch
                    {
                        Response.StatusCode = 416;  // 范围无效。
                        return false;
                    }
                    if (offset < 0 || offset >= fileLength)
                    {
                        Response.StatusCode = 416;  // 范围无效。
                        return false;
                    }
                }

                if (offset > 0)
                {
                    Response.AddHeader(Header_Content_Range, String.Format(Header_Content_Range_Value_Format, offset, fileLength - 1, fileLength));
                }
                inStream.Seek(offset, SeekOrigin.Begin);

                byte[] data = new byte[CacheSize];
                int len = 0;
                while (Response.IsClientConnected && (len = inStream.Read(data, 0, CacheSize)) > 0)
                {
                    Response.OutputStream.Write(data, 0, len);
                    if (sleepTime > 1)
                        System.Threading.Thread.Sleep(sleepTime);
                }
                return true;
            }
            catch
            {
                Response.StatusCode = 417; // 执行失败。
                return false;
            }
            finally
            {
                if (inStream != null)
                    inStream.Close();
            }
        }

        /// <summary>
        /// 暂未实现。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public bool Upload(HttpContext context, string filePath)
        {
            return false;
        }

        private string GetMD5Hash(Stream stream)
        {
            MD5 md5 = MD5.Create();
            byte[] data = md5.ComputeHash(stream);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sb.Append(data[i].ToString("x2"));
            }
            return sb.ToString();
        }
    }
}